fix(themes): handle CC >=2.1.92 assembly format (fixes objArrMatch + obj prefix bugs)#701
Conversation
In CC >=2.1.92 the theme options are no longer a single flat array
[{label:"Dark mode",value:"dark"},…]. Instead only the "auto" option
lives in one variable (e.g. HH=[{label:"Auto…",value:"auto"}]) and
the remaining built-in themes are individual variables (DH, YH, PH…)
that are spread together in a React-memo assembly expression:
e=[...HH,DH,YH,PH,t,o,_H,...U.map(kB1),...FH]
Two bugs result from the old code on the new format:
1. objArr replacement only swapped HH (the auto option), leaving all
built-in theme variables (DH,YH,…) in the spread, so the selector
showed tweakcc themes *plus* all built-in themes.
2. obj replacement always emitted `return{…}` as the prefix, turning
the module-level variable assignment `eB1={…}` into an invalid
return statement at the top level.
Fix:
- Detect new-assembly format when objArr has exactly 1 item and a
matching assembly spread is found. In that case objArr.startIndex
and endIndex span the prefix `[...HH,DH,…,themeN,` (everything up
to the custom-themes spread `...U.map(…)`). writeThemes emits the
tweakcc theme array without a closing `]` so the original suffix
`...U.map(kB1),...FH]` completes the expression — preserving CC's
own custom-theme and new-theme-creation entries.
- Extract and preserve the original obj prefix (`return` or `VAR=`)
so the replacement uses the correct syntax for both old and new CC.
Closes Piebald-AI#665
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughgetThemesLocation and writeThemes were updated to handle CC ≥2.1.92 assembly encodings: ChangesTheme patching logic
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 6/8 reviews remaining, refill in 13 minutes and 27 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/patches/themes.ts (2)
122-132:objPrefixviaindexOf('{')is safe for current themes but brittle.
objMatch[0].slice(0, objMatch[0].indexOf('{'))relies on the first{being the object-literal opener. BecauseobjPatonly accepts values starting withAuto |Dark|Light|Monochromefollowed by[^"]*, a theme name containing{would split the prefix incorrectly. No current CC theme does this, so it's a latent concern rather than a bug — but using the capture group of the prefix alternation would be more robust:♻️ Use an explicit capture for the prefix
const objPat = - /(?:return|[$\w]+=)\{(?:"?(?:[$\w-]+)"?:"(?:Auto |Dark|Light|Monochrome)[^"]*",?)+\}/; + /(return|[$\w]+=)\{(?:"?(?:[$\w-]+)"?:"(?:Auto |Dark|Light|Monochrome)[^"]*",?)+\}/; const objMatch = oldFile.match(objPat); if (!objMatch || objMatch.index == undefined) { console.error('patch: themes: failed to find objMatch'); return null; } - // Preserve the original prefix (either "return" or "VARNAME=") - const objPrefix = objMatch[0].slice(0, objMatch[0].indexOf('{')); + // Preserve the original prefix (either "return" or "VARNAME=") + const objPrefix = objMatch[1];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/themes.ts` around lines 122 - 132, The current extraction of objPrefix uses objMatch[0].slice(0, objMatch[0].indexOf('{')), which can break if a theme value contains a '{'; update the regex objPat to capture the prefix (the alternation that yields "return" or the variable assignment) as a dedicated capture group and then set objPrefix from that capture (e.g., use objMatch[1] instead of slicing), ensuring you still validate objMatch exists before reading the capture; keep references to objPat, objMatch and objPrefix when making the change.
92-92:objArrItemCountregex counts bothvalue:and"value":occurrences — confirm no collisions inside label text.
(objArrMatch[0].match(/"?value"?:/g) || []).lengthcounts item entries assumingvalue:appears once per item. That holds for the known CC emissions ("Dark mode", "Light mode", "Auto (match terminal)", "Monochrome"), but any future label containing the literal substringvalue:would inflate the count and skip the assembly-prefix branch. A stricter count like\{"?label"?:/g(matching the object-opening prefix) would avoid that class of false positive with no extra cost.- const objArrItemCount = (objArrMatch[0].match(/"?value"?:/g) || []).length; + const objArrItemCount = (objArrMatch[0].match(/\{"?label"?:/g) || []).length;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/themes.ts` at line 92, The current count in objArrItemCount uses a permissive pattern that matches both value: and "value": and can be inflated by label text containing that substring; update the counting logic that computes objArrItemCount from objArrMatch[0] to detect actual object entries by matching the object-opening label key instead (i.e., count occurrences of the object prefix for label rather than value), for example by using a pattern that looks for '{"label":' or unquoted label keys, and keep the rest of the branch logic (the assembly-prefix decision) unchanged so it reliably reflects the number of items.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/patches/themes.ts`:
- Around line 93-117: When objArrItemCount === 1 the code currently falls back
silently if varNameMatch or assemblyMatch fails, which can reintroduce Bug 1;
update the block handling the single-item case so that if varNameMatch is falsy
or assemblyMatch is falsy you log a clear diagnostic (including
autoVarName/assemblyPat context) and return null instead of leaving
objArrLocation pointing at the single-item array. Specifically, in the branch
that computes varNameMatch/autoVarName and then assemblyMatch (using assemblyPat
and oldFile), add explicit checks immediately after each match and bail out with
a logged error/diagnostic and a null return when a required match is missing.
Ensure you reference objArrItemCount, varNameMatch, assemblyMatch, autoVarName,
assemblyPat and objArrLocation when locating the code to change.
---
Nitpick comments:
In `@src/patches/themes.ts`:
- Around line 122-132: The current extraction of objPrefix uses
objMatch[0].slice(0, objMatch[0].indexOf('{')), which can break if a theme value
contains a '{'; update the regex objPat to capture the prefix (the alternation
that yields "return" or the variable assignment) as a dedicated capture group
and then set objPrefix from that capture (e.g., use objMatch[1] instead of
slicing), ensuring you still validate objMatch exists before reading the
capture; keep references to objPat, objMatch and objPrefix when making the
change.
- Line 92: The current count in objArrItemCount uses a permissive pattern that
matches both value: and "value": and can be inflated by label text containing
that substring; update the counting logic that computes objArrItemCount from
objArrMatch[0] to detect actual object entries by matching the object-opening
label key instead (i.e., count occurrences of the object prefix for label rather
than value), for example by using a pattern that looks for '{"label":' or
unquoted label keys, and keep the rest of the branch logic (the assembly-prefix
decision) unchanged so it reliably reflects the number of items.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 80c4073a-c241-48d0-a7b2-98ba8099b032
📒 Files selected for processing (1)
src/patches/themes.ts
- Use capture group in objPat to extract prefix instead of indexOf('{')
- Count items with /\{"?label"?:/g to avoid false positives from label text
- Return null with diagnostics when varNameMatch/assemblyMatch fail in
single-item case, preventing silent fallback to incorrect HH-only patch
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/patches/themes.ts (2)
107-110: Minor:{2,}may be slightly over-constrained.The assembly pattern requires ≥2 intermediate theme identifiers between
...autoVarand the next spread. Current CC has 6+ built-ins (DH,YH,PH,t,o,_H), so this works and is unlikely to overmatch. If a future CC release ever reduces built-ins to 1 (e.g.[...HH,DH,...U.map(...)]), detection will fail-safe tonullwith the clear error you added — not a regression, just something to keep in mind. Relaxing to{1,}would be more forgiving at the (small) cost of stricter reliance on the trailing,\.\.\.anchor; either is defensible.The ReDoS warning from the static analyzer on this
new RegExpis a false positive:autoVarNameis bounded by[A-Za-z_$][\w$]*and the only metachar that could appear ($) is explicitly escaped, so the constructed pattern is always safe.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/themes.ts` around lines 107 - 110, The assemblyPat RegExp in src/patches/themes.ts is too strict by requiring at least two intermediate identifiers between the ...autoVar spread and the next spread; update the pattern used to build assemblyPat (the RegExp constructed around autoVarName) to allow one or more intermediates (change the quantifier from {2,} to {1,}) so assemblies with a single built-in identifier are matched; leave the escaped metacharacters and surrounding anchors intact (no other logic changes) to preserve safety against ReDoS.
196-217: Add test coverage for the assembly prefix path.The splicing logic traces correctly: the replaced span ends at the comma before
...U.map(...), and the emitted fragment[{...themes...},joins cleanly with the untouched suffix, yielding the expected output. The legacy path (full-array replacement) is preserved whenisAssemblyPrefixis falsy.However, themes.ts has no corresponding test file, unlike other patch files (systemPrompts.test.ts, voiceMode.test.ts, etc.). Since both the regex detection and the assembly splicing are critical to correct operation, a test fixture covering both the assembly path (with
isAssemblyPrefix: true) and the legacy path would harden this against future Claude Code format changes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/patches/themes.ts` around lines 196 - 217, Add unit tests for src/patches/themes.ts to cover both assembly-prefix and legacy replacement paths: create fixtures exercising the code that computes objArr (use themes.map -> themeItems), toggling locations.objArr.isAssemblyPrefix true and false, and assert the produced newFile fragments join correctly with the untouched suffix (verify the emitted open-array case ends with a comma and the full-array case contains the closing bracket). Target the functions/logic around locations.objArr, objArr, and the showDiff call so the regex detection and splicing behavior are validated, mirroring existing test patterns used in systemPrompts.test.ts and voiceMode.test.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/patches/themes.ts`:
- Around line 107-110: The assemblyPat RegExp in src/patches/themes.ts is too
strict by requiring at least two intermediate identifiers between the ...autoVar
spread and the next spread; update the pattern used to build assemblyPat (the
RegExp constructed around autoVarName) to allow one or more intermediates
(change the quantifier from {2,} to {1,}) so assemblies with a single built-in
identifier are matched; leave the escaped metacharacters and surrounding anchors
intact (no other logic changes) to preserve safety against ReDoS.
- Around line 196-217: Add unit tests for src/patches/themes.ts to cover both
assembly-prefix and legacy replacement paths: create fixtures exercising the
code that computes objArr (use themes.map -> themeItems), toggling
locations.objArr.isAssemblyPrefix true and false, and assert the produced
newFile fragments join correctly with the untouched suffix (verify the emitted
open-array case ends with a comma and the full-array case contains the closing
bracket). Target the functions/logic around locations.objArr, objArr, and the
showDiff call so the regex detection and splicing behavior are validated,
mirroring existing test patterns used in systemPrompts.test.ts and
voiceMode.test.ts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3ad2bb94-7afc-4477-af0b-33ae595057de
📒 Files selected for processing (1)
src/patches/themes.ts
Problem
Fixes #665.
The themes patch fails on Claude Code >=2.1.92 with two bugs. PR #671 fixes Bug 2 but not Bug 1.
Bug 1 — objArr only patches the auto-option variable (not fixed by #671)
In CC >=2.1.92, theme options are no longer a single flat array. Only the "auto" option lives in one variable (e.g.
HH=[{label:\"Auto (match terminal)\",value:\"auto\"}]). The remaining built-in themes are individual variables spread in a React-memo cache guard:The old code replaced only
HH, soDH,YH,…stayed in the spread — resulting in tweakcc themes plus all built-in themes showing in the selector.Bug 2 — obj replacement emits
return{…}at module level (also fixed by #671)The old code always prefixed the name-mapping object with
return. In the new format the mapping is a module-level variable assignment (eB1={auto:\"Auto…\",dark:\"Dark mode\",…}), so replacing it withreturn{…}produces a syntax error.Fix
src/patches/themes.tsonly:Assembly-prefix detection (Bug 1): when
objArrhas exactly 1 item (just "auto"), look for the assembly expression[...autoVar,t1,t2,…,...X.map(. SetobjArrstartIndex/endIndex to span only the prefix[...HH,DH,…,_H,(everything before the custom-themes spread).writeThemesemits the tweakcc theme array without the closing]so the existing suffix...U.map(kB1),...FH]completes the expression — CC's own custom-theme and new-theme-creation entries are preserved.Prefix extraction (Bug 2): extract the original prefix of the obj match (
returnorVARNAME=) and use it in the replacement instead of always hard-codingreturn. (Same approach as CC 2.1.97 compatibility + new patches #671.)Testing
native-claudejs-orig.js).eB1={"winter":"Winter"}(correct assignment),[{"label":"Winter","value":"winter"},...U.map(kB1),...FH](correct array, built-in themes removed, CC custom themes preserved).npx vitest run).tsc --noEmit).objArr.Summary by CodeRabbit